/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Long running shader stress tests.
 *//*--------------------------------------------------------------------*/

#include "es3sLongRunningShaderTests.hpp"

#include "gluShaderProgram.hpp"
#include "gluShaderUtil.hpp"
#include "gluDrawUtil.hpp"

#include "tcuRenderTarget.hpp"
#include "tcuVector.hpp"
#include "tcuTestLog.hpp"

#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles3
{
namespace Stress
{

using tcu::TestLog;
using tcu::Vec2;
using std::vector;

namespace
{

enum LoopType
{
	LOOPTYPE_FOR = 0,
	LOOPTYPE_WHILE,
	LOOPTYPE_DO_WHILE,

	LOOPTYPE_LAST
};

enum IterCountType
{
	ITERCOUNTTYPE_STATIC = 0,
	ITERCOUNTTYPE_UNIFORM,
	ITERCOUNTTYPE_DYNAMIC,

	ITERCOUNTTYPE_LAST
};

class LongRunningShaderCase : public TestCase
{
public:
	struct Params
	{
		const char*			name;
		const char*			description;
		glu::ShaderType		shaderType;
		LoopType			loopType;
		IterCountType		iterCountType;
		int					numInvocations;
		int					minLoopIterCount;
		int					maxLoopIterCount;
	};

								LongRunningShaderCase		(Context& context, const Params* params);
								~LongRunningShaderCase		(void);

	void						init						(void);
	void						deinit						(void);
	IterateResult				iterate						(void);

private:
								LongRunningShaderCase		(const LongRunningShaderCase&);
	LongRunningShaderCase&		operator=					(const LongRunningShaderCase&);

	static glu::ProgramSources	genSources					(const Params& params);
	static deUint32				getSeed						(const Params& params);

	const Params* const			m_params;
	const int					m_numCaseIters;

	glu::ShaderProgram*			m_program;
	int							m_caseIterNdx;
};

LongRunningShaderCase::LongRunningShaderCase (Context& context, const Params* params)
	: TestCase			(context, params->name, params->description)
	, m_params			(params)
	, m_numCaseIters	(5)
	, m_program			(DE_NULL)
	, m_caseIterNdx		(0)
{
}

LongRunningShaderCase::~LongRunningShaderCase (void)
{
	deinit();
}

glu::ProgramSources LongRunningShaderCase::genSources (const Params& params)
{
	const bool			isVertCase		= params.shaderType == glu::SHADERTYPE_VERTEX;
	std::ostringstream	vert, frag;

	vert << "#version 300 es\n"
		 << "in highp vec2 a_position;\n";

	frag << "#version 300 es\n";

	if (params.iterCountType == ITERCOUNTTYPE_DYNAMIC)
	{
		vert << "in highp int a_iterCount;\n";
		if (!isVertCase)
		{
			vert << "flat out highp int v_iterCount;\n";
			frag << "flat in highp int v_iterCount;\n";
		}
	}
	else if (params.iterCountType == ITERCOUNTTYPE_UNIFORM)
		(isVertCase ? vert : frag) << "uniform highp int u_iterCount;\n";

	if (isVertCase)
	{
		vert << "out mediump vec4 v_color;\n";
		frag << "in mediump vec4 v_color;\n";
	}

	frag << "out mediump vec4 o_color;\n";

	vert << "\nvoid main (void)\n{\n"
		 << "	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		 << "	gl_PointSize = 1.0;\n";

	if (!isVertCase && params.iterCountType == ITERCOUNTTYPE_DYNAMIC)
		vert << "	v_iterCount = a_iterCount;\n";

	frag << "\nvoid main (void)\n{\n";

	{
		const std::string	iterCount	= params.iterCountType == ITERCOUNTTYPE_DYNAMIC ? (isVertCase ? "a_iterCount" : "v_iterCount")	:
										  params.iterCountType == ITERCOUNTTYPE_UNIFORM ? "u_iterCount"									:
										  params.iterCountType == ITERCOUNTTYPE_STATIC	? de::toString(params.maxLoopIterCount)			: "<invalid>";
		const char* const	body		= "color = cos(sin(color*1.25)*0.8);";
		std::ostringstream&	op			= isVertCase ? vert : frag;

		op << "	mediump vec4 color = " << (isVertCase ? "a_position.xyxy" : "gl_FragCoord") << ";\n";

		if (params.loopType == LOOPTYPE_FOR)
		{
			op << "	for (highp int i = 0; i < " << iterCount << " || " << iterCount << " < 0; ++i)\n"
			   << "		" << body << "\n";
		}
		else if (params.loopType == LOOPTYPE_WHILE)
		{
			op << "	highp int i = 0;\n"
			   << "	while (i < " << iterCount << " || " << iterCount << " < 0) {\n"
			   << "		i += 1;\n"
			   << "		" << body << "\n"
			   << "	}\n";
		}
		else
		{
			DE_ASSERT(params.loopType == LOOPTYPE_DO_WHILE);
			op << "	highp int i = 0;\n"
			   << "	do {\n"
			   << "		i += 1;\n"
			   << "		" << body << "\n"
			   << "	} while (i <= " << iterCount << " || " << iterCount << " < 0);\n";
		}
	}

	if (isVertCase)
	{
		vert << "	v_color = color;\n";
		frag << "	o_color = v_color;\n";
	}
	else
		frag << "	o_color = color;\n";

	vert << "}\n";
	frag << "}\n";

	return glu::ProgramSources() << glu::VertexSource(vert.str()) << glu::FragmentSource(frag.str());
}

void LongRunningShaderCase::init (void)
{
	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), genSources(*m_params));

	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
	{
		deinit();
		TCU_FAIL("Failed to compile shader program");
	}

	m_caseIterNdx = 0;

	if (m_params->iterCountType != ITERCOUNTTYPE_STATIC)
	{
		m_testCtx.getLog() << TestLog::Message << "Loop iteration counts in range: [" << m_params->minLoopIterCount
											   << ", " << m_params->maxLoopIterCount << "]"
						   << TestLog::EndMessage;
	}

	m_testCtx.getLog() << TestLog::Message << "Number of vertices and fragments: " << m_params->numInvocations << TestLog::EndMessage;

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass"); // Test will pass or timeout, unless driver/device crashes.
}

void LongRunningShaderCase::deinit (void)
{
	delete m_program;
	m_program = DE_NULL;
}

void genPositions (const tcu::RenderTarget& renderTarget, int numPoints, Vec2* positions)
{
	const int	width		= renderTarget.getWidth();
	const int	height		= renderTarget.getHeight();

	if (width*height < numPoints)
		throw tcu::NotSupportedError("Too small viewport to fit all test points");

	for (int pointNdx = 0; pointNdx < numPoints; pointNdx++)
	{
		const int		xi		= pointNdx % width;
		const int		yi		= pointNdx / height;
		const float		xf		= 2.0f * ((float(xi) + 0.5f) / float(width)) - 1.0f;
		const float		yf		= 2.0f * ((float(yi) + 0.5f) / float(height)) - 1.0f;

		positions[pointNdx] = Vec2(xf, yf);
	}
}

deUint32 LongRunningShaderCase::getSeed (const Params& params)
{
	const deUint32	seed	= deStringHash(params.name)
							^ deInt32Hash(params.shaderType)
							^ deInt32Hash(params.loopType)
							^ deInt32Hash(params.iterCountType)
							^ deInt32Hash(params.minLoopIterCount)
							^ deInt32Hash(params.maxLoopIterCount)
							^ deInt32Hash(params.numInvocations);
	return seed;
}

LongRunningShaderCase::IterateResult LongRunningShaderCase::iterate (void)
{
	const glw::Functions&			gl				= m_context.getRenderContext().getFunctions();
	de::Random						rnd				(getSeed(*m_params));
	vector<Vec2>					positions		(m_params->numInvocations);
	vector<int>						iterCounts		(m_params->iterCountType == ITERCOUNTTYPE_DYNAMIC ? m_params->numInvocations : 1);
	vector<glu::VertexArrayBinding>	vertexArrays;

	vertexArrays.push_back(glu::va::Float("a_position", 2, (int)positions.size(), 0, positions[0].getPtr()));
	if (m_params->iterCountType == ITERCOUNTTYPE_DYNAMIC)
		vertexArrays.push_back(glu::va::Int32("a_iterCount", 1, (int)iterCounts.size(), 0, &iterCounts[0]));

	genPositions(m_context.getRenderTarget(), (int)positions.size(), &positions[0]);

	for (vector<int>::iterator i = iterCounts.begin(); i != iterCounts.end(); ++i)
		*i = rnd.getInt(m_params->minLoopIterCount, m_params->maxLoopIterCount);

	gl.useProgram(m_program->getProgram());

	if (m_params->iterCountType == ITERCOUNTTYPE_UNIFORM)
		gl.uniform1i(gl.getUniformLocation(m_program->getProgram(), "u_iterCount"), iterCounts[0]);

	glu::draw(m_context.getRenderContext(), m_program->getProgram(),
			  (int)vertexArrays.size(), &vertexArrays[0],
			  glu::pr::Points(m_params->numInvocations));

	m_caseIterNdx += 1;
	return (m_caseIterNdx < m_numCaseIters) ? CONTINUE : STOP;
}

} // anonymous

LongRunningShaderTests::LongRunningShaderTests (Context& context)
	: TestCaseGroup(context, "long_running_shaders",	"Long-running shader stress tests")
{
}

LongRunningShaderTests::~LongRunningShaderTests (void)
{
}

void LongRunningShaderTests::init (void)
{
	const int	numInvocations	= 4096;
	const int	shortLoopMin	= 5;
	const int	shortLoopMax	= 10;
	const int	mediumLoopMin	= 10000;
	const int	mediumLoopMax	= 50000;
	const int	longLoopMin		= 100000;
	const int	longLoopMax		= 500000;

	static const LongRunningShaderCase::Params s_cases[] =
	{
		{ "short_for_vertex",					"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
		{ "short_for_fragment",					"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
		{ "short_while_vertex",					"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
		{ "short_while_fragment",				"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
		{ "short_do_while_vertex",				"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},
		{ "short_do_while_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	shortLoopMin,	shortLoopMax	},

		{ "medium_static_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_STATIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_static_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_STATIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_uniform_do_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_UNIFORM,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_uniform_for_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_UNIFORM,	numInvocations,	mediumLoopMin,	mediumLoopMax	},

		{ "medium_dynamic_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_dynamic_for_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_dynamic_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_dynamic_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_dynamic_do_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},
		{ "medium_dynamic_do_while_fragment",	"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	mediumLoopMin,	mediumLoopMax	},

		{ "long_static_while_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_STATIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_static_do_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_STATIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_uniform_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_UNIFORM,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_uniform_do_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_UNIFORM,	numInvocations,	longLoopMin,	longLoopMax		},

		{ "long_dynamic_for_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_dynamic_for_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_dynamic_while_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_dynamic_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_dynamic_do_while_vertex",		"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},
		{ "long_dynamic_do_while_fragment",		"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	longLoopMin,	longLoopMax		},

		{ "infinite_for_vertex",				"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
		{ "infinite_for_fragment",				"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_FOR,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
		{ "infinite_while_vertex",				"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
		{ "infinite_while_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_WHILE,		ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
		{ "infinite_do_while_vertex",			"",	glu::SHADERTYPE_VERTEX,		LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
		{ "infinite_do_while_fragment",			"",	glu::SHADERTYPE_FRAGMENT,	LOOPTYPE_DO_WHILE,	ITERCOUNTTYPE_DYNAMIC,	numInvocations,	-1,				-1				},
	};

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_cases); ndx++)
		addChild(new LongRunningShaderCase(m_context, &s_cases[ndx]));
}

} // Stress
} // gles3
} // deqp
